#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <http://www.zhengzhong.net/>
#
# Created on 2008-08-15.
# $Id$
#

import datetime
import logging
import random

from django.template import loader, Context
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, HttpResponseNotFound

from google.appengine.api import users
from google.appengine.api import memcache
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms as forms

from buggarden.foodsnroses.views import view_utils
from buggarden.foodsnroses.views import common_views
from buggarden.foodsnroses.models import RestoTag, Resto, RestoComment, RestoRating

#---------------------------------------------------------------------------------------------------

class PopularTag(object):

    def __init__(self, name, percentage):
        self.name = name
        self.percentage = percentage
    
    def __str__(self):
        return self.name
    
    def __unicode__(self):
        return self.name


def _create_tag_cloud(tag_list):
    tag_cloud = []
    if tag_list:
        total_count = 0
        for tag in tag_list:
            total_count += tag.count
        average_count = float(total_count) / len(tag_list)
        assert average_count > 0, 'Average tag count cannot be zero or negative.'
        for tag in tag_list:
            percentage = int(tag.count * 100 / average_count)
            percentage = min(max(percentage, 90), 140)
            tag_cloud.append(PopularTag(tag.name, percentage))
    tag_cloud.sort(lambda x, y: hash(x.name) - hash(y.name))
    return tag_cloud


def home(request):
    """Returns the Foods N' Roses webapp's home page."""
    
    # Fetch most popular restos.
    query = Resto.all()
    query.order('-average')
    most_popular_restos = query.fetch(3)
    
    # Fetch newly added restos.
    query = Resto.all()
    query.order('-submit_date')
    newly_added_restos = query.fetch(3)
    
    # Fetch most popular tags.
    query = RestoTag.all()
    query.order('-count')
    tag_cloud = _create_tag_cloud(query.fetch(20))
    
    # Return the HttpResponse.
    ctxt_dict = {
        'most_popular_restos': most_popular_restos,
        'newly_added_restos': newly_added_restos,
        'tag_cloud': tag_cloud,
    }
    ctxt = view_utils.create_context(request, ctxt_dict)
    tmpl = view_utils.get_template('home.html')
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def resto_catalog(request):
    """Returns the resto catalog."""
    
    restos = Resto.gql('ORDER BY name')
    ctxt_dict = {'restos': restos,}
    ctxt = view_utils.create_context(request, ctxt_dict)
    tmpl = view_utils.get_template('resto_catalog.html')
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

class AddRestoForm(forms.ModelForm):

    class Meta(object):
        model = Resto
        fields = (
            'name',
            'chinese_name',
            'address',
            'post_code',
            'city',
            'website',
            'email',
            'tel_1',
            'tel_2',
        )

def add_resto(request):
    """Handles request for adding a new resto to the datastore. After the resto has been added to
    the datastore successfully, this page redirects to the resto home page.
    """
    
    def generate_uid():
        timestamp = datetime.datetime.strftime(datetime.datetime.utcnow(), '%Y%m%d%H%M%S')
        random_suffix = '%04X' % (random.random() * 65536)
        return timestamp + random_suffix
    
    # This view is only accessible for signed-in users.
    user = users.get_current_user()
    if not user:
        message = 'Anonymous user is not allowed to add a resto.'
        return common_views.error(request, message)
    
    if request.method == 'GET':
        # Returns the add resto form.
        form = AddRestoForm()
        prompt = view_utils.Prompt(request.GET)
        ctxt_dict = {'form': form, 'prompt': prompt,}
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('add_resto.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
        # Add a new resto to the datastore.
        resto = Resto(uid=generate_uid(), address='NA', post_code='NA', city='NA', submitter=user)
        form = AddRestoForm(request.POST, instance=resto)
        try:
            if not form.is_valid():
                raise Exception('The submitted information is invalid.')
            resto = form.save()
            message = 'Resto "%s" has been added successfully.' % resto.name
            params = view_utils.create_info_param(message)
            url = view_utils.create_url('resto/%s/' % resto.uid, params)
            return HttpResponseRedirect(url)
        except Exception, exc:
            message = 'Failed to add resto: %s' % str(exc)
            logging.error(message)
            logging.exception(exc)
            prompt = view_utils.Prompt.create_error(message)
            ctxt_dict = {'form': form, 'prompt': prompt,}
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('add_resto.html')
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def resto(request, uid):

    # Get current user.
    user = users.get_current_user()
    
    # Get resto instance by uid.
    resto = Resto.gql('WHERE uid = :uid', uid=uid).get()
    if not resto:
        message = 'Resto "%s" you requested cannot be found.' % uid
        url = view_utils.create_error_url(message)
        return HttpResponseRedirect(url)
    
    # Get the current user's rating on the resto.
    query = RestoRating.all()
    query.ancestor(resto).filter('user = ', user)
    rating = query.get()
    
    # Generate a random ID for rendering Google Maps.
    map_id = 'map_%04X' % (random.random() * 65536)
    
    if request.method == 'GET':
    
        prompt = view_utils.Prompt(request.GET)
        ctxt_dict = {
            'resto': resto,
            'rating': rating,
            'map_id': map_id,
            'prompt': prompt,
        }
        ctxt = view_utils.create_context(request, ctxt_dict)
        tmpl = view_utils.get_template('resto.html')
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == 'POST':
    
        AJAX_BOXES = (
            'address',
            'ratings',
            'description',
            'map',
            'contact',
            'tags',
            'best_dishes',
        )
        ajax_box = request.POST.get('ajax_box')
        if ajax_box in AJAX_BOXES:
        
            try:
            
                # Update resto properties according to the 'ajax_box'.
                if ajax_box == 'address':
                    resto.address = request.POST.get('address')
                    resto.post_code = request.POST.get('post_code')
                    resto.city = request.POST.get('city')
                elif ajax_box == 'ratings':
                    if not user:
                        raise Exception('Anonymous user is not allowed to rate a resto.')
                    if rating:
                        raise Exception('You have already rated this resto.')
                    cuisine = int(request.POST.get('cuisine'))
                    economy = int(request.POST.get('economy'))
                    service = int(request.POST.get('service'))
                    rating_dict = {'cuisine': cuisine, 'economy': economy, 'service': service,}
                    for name, value in rating_dict.items():
                        if value <= 0 or value > 5:
                            raise Exception('Invalid rating for %s: %d' % (name, value))
                    resto.add_rating(cuisine=cuisine, economy=economy, service=service)
                    rating = RestoRating(
                        parent=resto,
                        cuisine=cuisine,
                        economy=economy,
                        service=service,
                        user=user,
                        ip_address=request.META.get('REMOTE_ADDR')
                    )
                    rating.put()
                elif ajax_box == 'description':
                    resto.description = request.POST.get('description')
                elif ajax_box == 'map':
                    resto.latitude = float(request.POST.get('latitude'))
                    resto.longitude = float(request.POST.get('longitude'))
                elif ajax_box == 'contact':
                    website = request.POST.get('website', '').strip()
                    if not website or website.lower() == 'http://':
                        website = None
                    email = request.POST.get('email', '').strip()
                    if not email:
                        email = None
                    resto.website = website
                    resto.email = email
                    resto.tel_1 = request.POST.get('tel_1')
                    resto.tel_2 = request.POST.get('tel_2')
                elif ajax_box == 'tags':
                    tags = request.POST.get('tags')
                    if tags:
                        resto.tag_list = tags.replace(',', ' ').replace(';', ' ').split()
                    else:
                        resto.tag_list = []
                elif ajax_box == 'best_dishes':
                    best_dishes = request.POST.get('best_dishes')
                    if best_dishes:
                        resto.best_dish_list = best_dishes.replace('\n', ' ').replace('\r', ' ') \
                                                          .replace(',', ' ').replace(';', ' ') \
                                                          .split()
                    else:
                        resto.best_dish_list = []
                
                # Update resto and rating (if necessary) in datastore.
                resto.put()
                ajax_error = None
            
            except Exception, exc:
                ajax_error = 'Failed to update resto: %s' % str(exc)
                logging.error(ajax_error)
            
            # Return an HTML snippet according to the 'ajax_box'.
            ctxt_dict = {
                'resto': resto,
                'rating': rating,
                'map_id': map_id,
                'ajax_error': ajax_error,
            }
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('ajax/resto_%s.html' % ajax_box)
            return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------

def resto_comments(request, uid):
    """This function manages comments on a resto."""
    
    resto = Resto.gql('WHERE uid=:uid', uid=uid).get()
    if not resto:
        message = 'Resto "%s" you requested cannot be found.' % uid
        url = view_utils.create_error_url(message)
        return HttpResponseRedirect(url)
    
    ajax_error = None
    
    if request.method == 'GET':
        # Get the (optional) page number from query.
        try:
            page_number = int(request.GET.get('page', 1))
            if page_number <= 0:
                page_number = 1
        except:
            page_number = 1
    
    elif request.method == 'POST':
        # When user adds a comment, return the first page of comments.
        page_number = 1
        try:
            # Only signed-in user can add a comment on a resto.
            user = users.get_current_user()
            if not user:
                raise Exception('Anonymous user is not allowed to add comment.')
            # Comment without content is not allowed.
            content = request.POST.get('content', '').strip()
            if not content:
                raise Exception('The comment is empty.')
            # Create a comment and save it to the datastore.
            comment = RestoComment(parent=resto, content=content, author=user)
            comment.put()
            ajax_error = None
        except Exception, exc:
            ajax_error = 'Failed to add comment to resto "%s": %s' % (uid, str(exc))
    
    # Fetch comments on the page specified by 'page_number'.
    COMMENTS_PER_PAGE = 6
    query = RestoComment.all()
    query.ancestor(resto).order('-post_date')
    comments_count = query.count(1000)
    comments = query.fetch(COMMENTS_PER_PAGE, (page_number - 1) * COMMENTS_PER_PAGE)
    # Calculate the previous/next page number, and set to None if not exists.
    if page_number > 1:
        prev_page_number = page_number - 1
    else:
        prev_page_number = None
    
    if page_number * COMMENTS_PER_PAGE < comments_count:
        next_page_number = page_number + 1
    else:
        next_page_number = None
    # Return the HttpResponse.
    ctxt_dict = {
        'resto': resto,
        'comments': comments,
        'page_number': page_number,
        'prev_page_number': prev_page_number,
        'next_page_number': next_page_number,
        'ajax_error': ajax_error,
    }
    ctxt = view_utils.create_context(request, ctxt_dict)
    tmpl = view_utils.get_template('ajax/resto_comments.html')
    return HttpResponse(tmpl.render(ctxt))
            

#---------------------------------------------------------------------------------------------------

def resto_photo(request, uid):
    """Returns the raw resto photo image (GET); Updates resto photo in datastore (POST)."""
    
    resto = Resto.gql('WHERE uid=:uid', uid=uid).get()
    if not resto:
        message = 'Resto "%s" you requested cannot be found.' % uid
        url = view_utils.create_error_url(message)
        return HttpResponseRedirect(url)
    
    if request.method == 'GET':
        if resto.photo_type and resto.photo_data:
            return HttpResponse(resto.photo_data, mimetype=resto.photo_type)
        else:
            return HttpResponseNotFound('Cannot find photo for resto "%s".' % uid)
    
    elif request.method == 'POST':
        try:
            photo_file = request.FILES.get('photo')
            if photo_file:
                photo_file.open()
                try:
                    resto.photo_type = photo_file.content_type
                    resto.photo_data = photo_file.read()
                finally:
                    photo_file.close()
            elif request.POST.get('delete') == 'yes':
                resto.photo_type = None
                resto.photo_data = None
            resto.put()
        except Exception, exc:
            message = 'Failed to update resto photo: %s' % str(exc)
            logging.error(message)
        url = view_utils.create_url('resto/%s/' % resto.uid)
        return HttpResponseRedirect(url)

#---------------------------------------------------------------------------------------------------

def search(request, name):
    """Searches restos by name and value."""
    
    PROPERTIES = {
        'post_code': 'post_code',
        'city': 'city',
        'tag': 'tag_list',
        'dish': 'best_dish_list',
    }
    
    if request.method == 'GET':
        value = request.GET.get('value')
        try:
            if name not in PROPERTIES:
                raise Exception('Invalid searching criteria "%s".' % name)
            # TODO: we need to cast explicitly the query string from unicode to str.
            query_string = 'WHERE %s=:value' % PROPERTIES[name]
            restos = Resto.gql(str(query_string), value=value)
            ctxt_dict = {'name': name, 'value': value, 'restos': restos,}
            ctxt = view_utils.create_context(request, ctxt_dict)
            tmpl = view_utils.get_template('resto_search_results.html')
            return HttpResponse(tmpl.render(ctxt))
        except Exception, exc:
            message = 'Failed to search by %s ("%s"): %s' % (name, value, str(exc))
            logging.error(message)
            url = view_utils.create_error_url(message)
            return HttpResponseRedirect(url)














